﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace XML_VML
{
    public class ZoomBox : Control
    {
        private Thumb _zoomThumb;
        private Canvas _zoomCanvas;
        private Slider _zoomSlider;
        private ScaleTransform _scaleTransform;
        private DesignerCanvas _designerCanvas;

        public ScrollViewer ScrollViewer
        {
            get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
            set { SetValue(ScrollViewerProperty, value); }
        }

        public static readonly DependencyProperty ScrollViewerProperty =
            DependencyProperty.Register("ScrollViewer", typeof(ScrollViewer), typeof(ZoomBox));

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (ScrollViewer == null)
                return;

            _designerCanvas = ScrollViewer.Content as DesignerCanvas;
            if (_designerCanvas == null)
                throw new Exception("DesignerCanvas must not be null!");
            
            _zoomThumb = Template.FindName("PART_ZoomThumb", this) as Thumb;
            if (_zoomThumb == null)
                throw new Exception("PART_ZoomThumb template is missing!");

            _zoomCanvas = Template.FindName("PART_ZoomCanvas", this) as Canvas;
            if (_zoomCanvas == null)
                throw new Exception("PART_ZoomCanvas template is missing!");

            _zoomSlider = Template.FindName("PART_ZoomSlider", this) as Slider;
            if (_zoomSlider == null)
                throw new Exception("PART_ZoomSlider template is missing!");

            _designerCanvas.LayoutUpdated += DesignerCanvasLayoutUpdated;

            _zoomThumb.DragDelta += ThumbDragDelta;

            _zoomSlider.ValueChanged += ZoomSliderValueChanged;

            _scaleTransform = new ScaleTransform();
            _designerCanvas.LayoutTransform = _scaleTransform;
        }

        private void ZoomSliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            var scale = e.NewValue / e.OldValue;

            var halfViewportHeight = ScrollViewer.ViewportHeight / 2;
            var newVerticalOffset = ((ScrollViewer.VerticalOffset + halfViewportHeight) * scale - halfViewportHeight);

            var halfViewportWidth = ScrollViewer.ViewportWidth / 2;
            var newHorizontalOffset = ((ScrollViewer.HorizontalOffset + halfViewportWidth) * scale - halfViewportWidth);

            _scaleTransform.ScaleX *= scale;
            _scaleTransform.ScaleY *= scale;

            ScrollViewer.ScrollToHorizontalOffset(newHorizontalOffset);
            ScrollViewer.ScrollToVerticalOffset(newVerticalOffset);
        }

        private void ThumbDragDelta(object sender, DragDeltaEventArgs e)
        {
            double scale, xOffset, yOffset;
            InvalidateScale(out scale, out xOffset, out yOffset);

            ScrollViewer.ScrollToHorizontalOffset(ScrollViewer.HorizontalOffset + e.HorizontalChange / scale);
            ScrollViewer.ScrollToVerticalOffset(ScrollViewer.VerticalOffset + e.VerticalChange / scale);
        }

        private void DesignerCanvasLayoutUpdated(object sender, EventArgs e)
        {
            double scale, xOffset, yOffset;
            InvalidateScale(out scale, out xOffset, out yOffset);

            _zoomThumb.Width = ScrollViewer.ViewportWidth * scale;
            _zoomThumb.Height = ScrollViewer.ViewportHeight * scale;

            Canvas.SetLeft(_zoomThumb, xOffset + ScrollViewer.HorizontalOffset * scale);
            Canvas.SetTop(_zoomThumb, yOffset + ScrollViewer.VerticalOffset * scale);
        }

        private void InvalidateScale(out double scale, out double xOffset, out double yOffset)
        {
            // designer canvas size
            var w = _designerCanvas.ActualWidth * _scaleTransform.ScaleX;
            var h = _designerCanvas.ActualHeight * _scaleTransform.ScaleY;

            // zoom canvas size
            var x = _zoomCanvas.ActualWidth;
            var y = _zoomCanvas.ActualHeight;

            var scaleX = x / w;
            var scaleY = y / h;

            scale = (scaleX < scaleY) ? scaleX : scaleY;

            xOffset = (x - scale * w) / 2;
            yOffset = (y - scale * h) / 2;
        }
    }
}
